/*
 * ﻿Copyright (C) 2013-2014 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.injection;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple abstract aspect to return different value than one has been set to member field or been returned 
 * by (getter) member method of target. <br />
 * This is not injector; just for returning other object than original. <br />
 * An example usage will be to deceive the value of static member field only for certain circumstance 
 * without changing its actual value. 
 * 
 * @author Arata Y.
 */
@Aspect
public abstract class AbstractSwapperAspect {
	private Logger logger = LoggerFactory.getLogger( this.getClass());
		public Logger getLogger() {
			return logger;
		}
		protected void setLogger( final Logger logger) {
			this.logger = logger;
		}

	public static final boolean IsOriginalPrecedenceDefaultValue = true;
		public static boolean getIsOriginalPrecedenceDefaultValue() {
			return IsOriginalPrecedenceDefaultValue;
		}
	private boolean isOriginalPrecedence 
	= AbstractSwapperAspect.getIsOriginalPrecedenceDefaultValue();
		/**
		 * Getter for value of switch controlling whether returning original object 
		 * (what will be obtained by proceeding from given point-cut) or one being returned by 
		 * <code>{@link #getReplacement()}</code> method.<br />
		 * Default to {@value #IsOriginalPrecedenceDefaultValue}.
		 * @return true for returning original object (if it's null then returning value of 
		 * <code>{@link #isOriginalPrecedence()}</code> method will be referred to decide whether 
		 * returning null or object being returned by <code>{@link #getReplacement()}</code> method, 
		 * false for returning object being returned by <code>{@link #getReplacement()}</code> method.
		 * @see #isReturningOriginalNull()
		 */
		public boolean isOriginalPrecedence() {
			return isOriginalPrecedence;
		}
		/**
		 * Setter for value of switch controlling whether returning original object 
		 * (what will be obtained by proceeding from given point-cut) or one being returned by 
		 * <code>{@link #getReplacement()}</code> method.<br />
		 * @param isOriginalPrecedence : true for returning original object (if it's null then returning 
		 * value of <code>{@link #isOriginalPrecedence()}</code> method will be referred to decide whether 
		 * returning null or object being returned by <code>{@link #getReplacement()}</code> method, 
		 * false for returning object being returned by <code>{@link #getReplacement()}</code> method.
		 * @see #isReturningOriginalNull()
		 */
		public void setOriginalPrecedence( boolean isOriginalPrecedence) {
			this.isOriginalPrecedence = isOriginalPrecedence;
		}
		
	public static final boolean IsReturningOriginalNullDefaultValue = false;
		public static boolean getIsReturningOriginalNullDefaultValue() {
			return IsReturningOriginalNullDefaultValue;
		}
	private boolean isReturningOriginalNull = AbstractSwapperAspect.IsReturningOriginalNullDefaultValue;
		/**
		 * Getter for value of switch controlling whether returning null or returning object being 
		 * retunred by <code>{@link #getReplacement()}</code> method when original value (what will be 
		 * obtained by proceeding from given point-cut) is null. <br />  
		 * Default to {@value #IsReturningOriginalNullDefaultValue}
		 * 
		 * @return true when allowing returning null when original value is null, false when allowing 
		 * returning object being obtained by <code>{@link #getReplacement()}</code> method. 
		 * @see #isOriginalPrecedence()
		 * @see #setOriginalPrecedence(boolean)
		 */
		public boolean isReturningOriginalNull() {
			return isReturningOriginalNull;
		}
		/**
		 * Setter for value of switch controlling whether returning null or returning object being 
		 * retunred by <code>{@link #getReplacement()}</code> method when original value (what will be 
		 * obtained by proceeding from given point-cut) is null. <br />  
		 * 
		 * @param isReturningOriginalNull : true to allow returning null when original value is null, 
		 * false to return object being obtained by <code>{@link #getReplacement()}</code> method. 
		 * @see #isOriginalPrecedence()
		 * @see #setOriginalPrecedence(boolean)
		 */
		public void setReturningOriginalNull(boolean isReturningOriginalNull) {
			this.isReturningOriginalNull = isReturningOriginalNull;
		}
	/**
	 * To provide alternate object for original one what will be gained by proceeding the point-cut 
	 * specified with <code>{@link #pointcutAtGettingOriginal()}</code> method. <br />
	 * Sub (non abstract) aspect must override this.<br />
	 * 
	 * @return Alternate object being swapped for original one.
	 * @see #aroundGettingOriginal(ProceedingJoinPoint)
	 * @see #isOriginalPrecedence()
	 * @see #isReturningOriginalNull()
	 */
	public abstract Object getReplacement();

	/**
	 * Abstract point-cut for picking up value of swapping target.<br />
	 * The value will be swapped to one being returned by <code>{@link #getReplacement()}</code> method 
	 * in <code>{@link #aroundGettingOriginal(ProceedingJoinPoint)}</code> advise method. <br />
	 * Sub (non-abstract) aspect must override this.
	 */
	@Pointcut
	public abstract void pointcutAtGettingOriginal();
	
	/**
	 * Around advise method to swap target object being obtained by proceeding from the point-cut specified 
	 * with <code>{@link #pointcutAtGettingOriginal()}</code> method to one returned by 
	 * <code>{@link #getReplacement()}</code> method.<br />
	 * 
	 * @param proceedingJoinPoint : Join-point at point-cut specified with 
	 * <code>{@link #pointcutAtGettingOriginal()}</code> method
	 * @return Object returned by <code>{@link #getReplacement()}</code> method or 
	 * original object being obtained by proceeding from the point-cut specified with 
	 * <code>{@link #pointcutAtGettingOriginal()}</code> method according to the 
	 * settings with <code>{@link #isOriginalPrecedence()}</code> method and 
	 * <code>{@link #isReturningOriginalNull()}</code> method.
	 */
	@Around( value = "pointcutAtGettingOriginal()")
	public Object aroundGettingOriginal( final ProceedingJoinPoint proceedingJoinPoint) {
		Object obj = null;
			if ( isOriginalPrecedence()) {
				try {
						if ( proceedingJoinPoint.getArgs().length < 1) {
							obj = proceedingJoinPoint.proceed();
						}
						else {
							obj 
							= proceedingJoinPoint.proceed( proceedingJoinPoint.getArgs());
						}
				} 
				catch( Throwable throwable) {
					if ( RuntimeException.class.isInstance( throwable)) {
						throw (RuntimeException)throwable;
					}
					else {
						throw new RuntimeException( throwable);
					}
				}
			}
		if ( ( obj == null) && !isReturningOriginalNull()) {
			obj = getReplacement();
		}
		
			final Logger logger = getLogger();
				if ( logger.isDebugEnabled()) {
					logger.debug(
							String.format(
									"Returning %1$s as result of proceeding the join-point from %2$s.",
									obj, 
									proceedingJoinPoint.toString())
							);
				}
		
		return obj;
	}
}
//TODO Change to definition by using parameterized generic abstract aspect once found out that AspectJ's annotation style supports it even with LTW.   